#include "AdministradorEstacionamiento.h"


AdministradorEstacionamiento::AdministradorEstacionamiento(int cantidadEstacionamientos, int cantidadLugares, int costoHora):
	administrando (true),
	entradasCerradas (false),
	cantidadEstacionamientos (cantidadEstacionamientos),
	cantidadLugares (cantidadLugares),
	costoHora (costoHora),
	colasEstacionamientos(vector<Cola<MensajeTicket> >()),
	colaAdministrador (Cola<MensajeTicket>((char*) ARCHIVO_COLA_ADMINISTRADOR, 1)),
	lugares (vector<vector<bool> >()),
	pidsEstacionamientos (vector<pid_t>()),
	cantidadAutos (vector<int>()),
	montoFacturado (vector<int>()),
	cantidadTotalAutos (vector<int>()){

	// Registro de handlers de señales.
	SignalHandler::getInstance()->registrarHandler(SIGUSR1, this);
	SignalHandler::getInstance()->registrarHandler(SIGINT, this);
	SignalHandler::getInstance()->registrarHandler(SIGTERM, this);
	
	// Inicialización de colas de estacionamientos.
	for (int i = 0; i < cantidadEstacionamientos; i ++) {
		Cola<MensajeTicket> cola = Cola <MensajeTicket>((char*) ARCHIVO_COLAS_ESTACIONAMIENTOS, i) ;
		colasEstacionamientos.push_back(cola);
	}

	// Inicialización de los lugares de los estacionamientos.
	for (int i = 0; i < cantidadEstacionamientos; i++){
		this->lugares.push_back(vector<bool>());
		for (int j = 0; j < cantidadLugares; j++){
			this->lugares[i].push_back(false);
		}
	}

	// Inicialización de la cantidad de autos y monto facturado de los estacionamientos
	for (int i = 0; i < cantidadEstacionamientos; i ++) {
		this->cantidadAutos.push_back(0);
		this->montoFacturado.push_back(0);
		this->cantidadTotalAutos.push_back(0);
	}

	Log::getInstance()->loguear("Administrador: creado.");
}

AdministradorEstacionamiento::~AdministradorEstacionamiento() {
	SignalHandler::getInstance()->removerHandler(SIGUSR1);
	SignalHandler::getInstance()->removerHandler(SIGINT);
	SignalHandler::getInstance()->removerHandler(SIGTERM);
	SignalHandler::destruir();
}

int AdministradorEstacionamiento::inicializar(){
	return this->generarEstacionamientos();
}

void AdministradorEstacionamiento::destruir(){
	Log::getInstance()->loguear("Administrador destruyo la cola del administrador.");
	this->colaAdministrador.destruir();

	Log::getInstance()->loguear("Administrador: destruido.");
}

/**
 * Crea los procesos de los estacionamientos y los inicializa.
 */
int AdministradorEstacionamiento::generarEstacionamientos(){
	for (int i = 0; i < this->cantidadEstacionamientos; i++){
		pid_t pid = fork();
		if (pid == 0){
			Estacionamiento estacionamiento(i);
			if(estacionamiento.inicializar() == 0){
				estacionamiento.funcionar();
				estacionamiento.destruir();
			}
			return 1;
		} else {
			this->pidsEstacionamientos.push_back(pid);
		}
	}
	// Solo el proceso Administrador devuelve 0
	return 0;
}

/**
 * Handler de señales:
 * SIGUSR1: Señal que envia el terminador para finalizar la simulación.
 * SIGINT: Se ignora. El terminador la toma para iniciar la detención del programa ordenadamente
 * SIGTERM: Se ignora.  El terminador la toma para iniciar la detención del programa ordenadamente
 */
int AdministradorEstacionamiento::handleSignal(int signum){
	if (signum == SIGUSR1){
		Log::getInstance()->loguear("Administrador: me llego SIGUSR1. Aviso a los estacionamientos que cierren las entradas.");
		this->avisarEstacionamientosCerrarEntradas();
		this->verificarEstacionamientosVacios();

	} else if (signum == SIGINT){
		Log::getInstance()->loguear("Administrador: me llego SIGINT. No hago nada.");
		// La señal la toma el terminador para detener la ejecución ordenadamente.

	} else if (signum == SIGTERM){
		Log::getInstance()->loguear("Administrador: me llego SIGTERM. No hago nada.");
		// La señal la toma el terminador para detener la ejecución ordenadamente.

	}
	return 0;
}

/**
 * Envia señales a todos los estacionamientos para que cierren sus entradas.
 */
void AdministradorEstacionamiento::avisarEstacionamientosCerrarEntradas(){
	for (vector<pid_t>::iterator itEstacionamientos = this->pidsEstacionamientos.begin(); itEstacionamientos != this->pidsEstacionamientos.end() ; itEstacionamientos++){
		kill(*itEstacionamientos, SIGUSR1);
	}
	this->entradasCerradas = true;
}

/**
 * Avisa a un estacionamiento que cierre las salidas y espera a que se muera.
 */
void AdministradorEstacionamiento::avisarEstacionamientoCerrarSalidas(int numeroEstacionamiento){
	int state;
	kill(this->pidsEstacionamientos[numeroEstacionamiento], SIGUSR2);
	waitpid(this->pidsEstacionamientos[numeroEstacionamiento], &state, 0);
}

/**
 * Metodo que se ejecuta continuamente mientras el administrador funcione.
 */
void AdministradorEstacionamiento::administrarEstacionamientos(){
	while(this->administrando){
		this->leerColaAdministrador();
	}
	this->imprimirEstadisticas();
};
	
/**
 * Lee la cola de mensajes del administrador. Recibe varios tipos de mensajes:
 * ESTACIONAMIENTO_LIBERA_LUGAR: El estacionamiento informa que un lugar se libera.
 * ESTACIONAMIENTO_PIDE_LUGAR_P: El estacionamiento pide saber si tiene algún lugar libre.
 * CONSULTA_CANTIDAD_AUTOS_P: El proceso de consulta pide saber la cantidad de autos de
 * 	uno o todos los estacionamientos.
 * CONSULTA_MONTO_FACTURADO_P: El proceso de consulta pide saber el monto facturado de
 * 	uno o todos los estacionamiento.
 */
void AdministradorEstacionamiento::leerColaAdministrador(){
	MensajeTicket mensaje;
	int resultado = this->colaAdministrador.leer(-1 * CONSULTA_MONTO_FACTURADO_P, &mensaje);
	if(resultado != -1){
		if (mensaje.mtype == ESTACIONAMIENTO_LIBERA_LUGAR){
			this->liberarLugarEstacionamiento(mensaje.ticket);

		} else if (mensaje.mtype == ESTACIONAMIENTO_PIDE_LUGAR_P){
			int lugar = this->buscarLugarEstacionamiento(mensaje.ticket.getNumeroEstacionamiento());
			mensaje.mtype = ESTACIONAMIENTO_PIDE_LUGAR_R;
			mensaje.ticket.setLugarOcupado(lugar);
			this->colasEstacionamientos[mensaje.ticket.getNumeroEstacionamiento()].escribir(mensaje);

		} else if (mensaje.mtype == CONSULTA_CANTIDAD_AUTOS_P){
			this->enviarCantidadAutos(mensaje.ticket.getNumeroEstacionamiento());

		} else if (mensaje.mtype == CONSULTA_MONTO_FACTURADO_P){
			this->enviarMontoFacturado(mensaje.ticket.getNumeroEstacionamiento());

		}
	}
}

/**
 * Busca un lugar libre en un estacionamiento y lo marca como ocupado.
 * Si no encuentra ningún lugar devuelve -1.
 */
int AdministradorEstacionamiento::buscarLugarEstacionamiento(int numeroEstacionamiento){
	Log::getInstance()->loguear("Administrador: el estacionamiento "
			+ intToString(numeroEstacionamiento) + " solicita un lugar disponible.");

	int i = 0;
	int lugar = -1;
	while((i < this->cantidadLugares) && (lugar == -1)){
		if(! (this->lugares[numeroEstacionamiento][i])){
			this->lugares[numeroEstacionamiento][i] = true;
			this->cantidadAutos[numeroEstacionamiento]++;
			this->cantidadTotalAutos[numeroEstacionamiento]++;
			lugar = i;
		}
		i++;
	}
	return lugar;
}

/**
 * Marca como libre un lugar determinado de un estacionamiento.
 * Luego, si ya se han cerrado las entradas de los estacionamientos, verifica si no hay más
 * autos. En tal caso le avisa al estacionamiento para que cierre las salidas y verifica
 * si todos los estacionamientos no tienen más autos.
 */
void AdministradorEstacionamiento::liberarLugarEstacionamiento(Ticket ticket){
	Log::getInstance()->loguear("Administrador: se libera lugar "
			+ intToString(ticket.getLugarOcupado()) + " del estacionamiento "
			+ intToString(ticket.getNumeroEstacionamiento()) + ".");

	this->lugares[ticket.getNumeroEstacionamiento()][ticket.getLugarOcupado()] = false;

	this->cantidadAutos[ticket.getNumeroEstacionamiento()]--;
	this->montoFacturado[ticket.getNumeroEstacionamiento()] += this->costoHora * ticket.getHorasEstacionado();

	if(this->entradasCerradas){
		if (this->estacionamientoVacio(ticket.getNumeroEstacionamiento())){
			this->avisarEstacionamientoCerrarSalidas(ticket.getNumeroEstacionamiento());
			this->verificarCierreEstacionamientos();
		}
	}
}

/**
 * Devuelve true si no hay más autos en el estacionamiento.
 */
bool AdministradorEstacionamiento::estacionamientoVacio(int numeroEstacionamiento){
	bool vacio = true;
	for (int i = 0; i < this->cantidadLugares; i++){
		if (this->lugares[numeroEstacionamiento][i] == true){
			vacio = false;
		}
	}
	return vacio;
}

/**
 * Revisa que todos los estacionamientos no tengan más autos. En caso positivo pone el flag
 * 'administrando' en false para que el administrador finalice la ejecución.
 */
void AdministradorEstacionamiento::verificarCierreEstacionamientos(){
	bool todosEstacionamientosCerrados = true;
	for (int i = 0; i < this->cantidadEstacionamientos; i++){
		if (!this->estacionamientoVacio(i)){
			todosEstacionamientosCerrados = false;
		}
	}
	if (todosEstacionamientosCerrados){
		this->administrando = false;
	}
}

/**
 * Metodo que se ejecuta cuando se recibe la orden para finalizar la simulación y contempla
 * los casos de aquellos estacionamientos que no recibieron ningún auto durante la ejecución.
 * Se fija si hay algún estacionamiento vacio. En ese caso le avisa que cierre las salidas.
 */
void AdministradorEstacionamiento::verificarEstacionamientosVacios(){
	for (int i = 0; i < this->cantidadEstacionamientos; i++){
		if (this->estacionamientoVacio(i)){
			this->avisarEstacionamientoCerrarSalidas(i);
		}
	}
	this->verificarCierreEstacionamientos();
}

/**
 * Escribe en la cola del administrador un mensaje con la cantidad de autos de los
 * estacionamientos.
 * Si numeroEstacionamiento es -1 se escribe la información de todos los estacionamientos.
 * Sino se escribe la información de un solo estacionamiento.
 */
void AdministradorEstacionamiento::enviarCantidadAutos(int numeroEstacionamiento){
	MensajeTicket mensaje;
	mensaje.mtype = CONSULTA_CANTIDAD_AUTOS_R;

	if (numeroEstacionamiento == -1){
		// Se envía la información de todos los estacionamientos
		for (int i = 0; i < this->cantidadEstacionamientos; i++){
			mensaje.ticket.setNumeroEstacionamiento(i);
			mensaje.ticket.setLugarOcupado(this->cantidadAutos[i]);
			this->colaAdministrador.escribir(mensaje);
		}
	} else {
		// Se envía la información un solo estacionamiento
		mensaje.ticket.setNumeroEstacionamiento(numeroEstacionamiento);
		mensaje.ticket.setLugarOcupado(this->cantidadAutos[numeroEstacionamiento]);
		this->colaAdministrador.escribir(mensaje);
	}
}

/**
 * Escribe en la cola del administrador un mensaje con el monto facturado de los
 * estacionamientos.
 * Si numeroEstacionamiento es -1 se escribe la información de todos los estacionamientos.
 * Sino se escribe la información de un solo estacionamiento.
 */
void AdministradorEstacionamiento::enviarMontoFacturado(int numeroEstacionamiento){
	MensajeTicket mensaje;
	mensaje.mtype = CONSULTA_MONTO_FACTURADO_R;

	if (numeroEstacionamiento == -1){
		// Se envía la información de todos los estacionamientos
		for (int i = 0; i < this->cantidadEstacionamientos; i++){
			mensaje.ticket.setNumeroEstacionamiento(i);
			mensaje.ticket.setLugarOcupado(this->montoFacturado[i]);
			this->colaAdministrador.escribir(mensaje);
		}
	} else {
		// Se envía la información un solo estacionamiento
		mensaje.ticket.setNumeroEstacionamiento(numeroEstacionamiento);
		mensaje.ticket.setLugarOcupado(this->montoFacturado[numeroEstacionamiento]);
		this->colaAdministrador.escribir(mensaje);
	}
}

/**
 * Muestra por pantalla la cantidad total de autos que estacionaron en cada estacionamiento
 * y el monto facturado por los mismos.
 */
void AdministradorEstacionamiento::imprimirEstadisticas(){
	for (int i = 0; i < cantidadEstacionamientos; i++){
		Log::getInstance()->imprimir("Administrador: La cantidad total de autos que estacionaron en el estacionamiento "
				+ intToString(i) + " fue " + intToString(this->cantidadTotalAutos[i]) + ".");
	}
	for (int i = 0; i < cantidadEstacionamientos; i++){
		Log::getInstance()->imprimir("Administrador: El monto total facturado por el estacionamiento "
				+ intToString(i) + " es $" + intToString(this->montoFacturado[i]) + ".");
	}
}

